#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#include <map>
#include <string>

#include "mqttclient_communication.h" 
#include "device_communication.h"


// 存放所有在线设备的信息(设备ID、连接套接字)
static std::map<std::string, int> online_devs;



static int recv_line(int sock, char *buff, int size);
static void* do_device_comm(void *arg);


void device_comm(void)
{
    
    // 1.创建监听套接字
    int socklisten = socket(AF_INET, SOCK_STREAM, 0);

    if(-1 == socklisten)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    
    // 设置套接字的地址复用属性为1, 允许地址复用，从而实现快速重启服务器
    int opt_val = 1;
    setsockopt(socklisten, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val));

    // 2.绑定地址
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;   // 指定地址家族为Internet地址家族（ipv4）
    myaddr.sin_addr.s_addr = INADDR_ANY;  // 指定IP地址为本机任意IP地址
    //myaddr.sin_addr.s_addr = inet_addr("192.168.57.132");  // inet_addr 将字符串点分十进制IP地址转换为网络字节序形式的IP地址
    myaddr.sin_port = htons(9413);  // 指定端口号为9413

    if(-1 == bind(socklisten, (struct sockaddr*)&myaddr, sizeof(myaddr)))
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }
    
    // 3.监听
    if(-1 == listen(socklisten, 5))
    {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    int sockconn;
    struct sockaddr_in cliaddr;
    socklen_t addrlen = sizeof(cliaddr);
    struct timeval t;
    t.tv_sec = 10;
    t.tv_usec = 0;

    while(1)
    {
	    // 4.接受客户端连接请求

	    //int sockconn = accept(socklisten, NULL, NULL);   // 如果对地址不甘兴趣，就传NULL
	    sockconn = accept(socklisten, (struct sockaddr*)&cliaddr, &addrlen);   // 如果对客户端地址信息感兴趣


	    if(sockconn == -1)
	    {
		    perror("accpet");
		   continue;
	    }

	    // 创建一个新线程
	    pthread_t tid;

	    if(pthread_create(&tid, NULL, do_device_comm, (void*)(long)sockconn))
	    {
		    perror("pthread_create");
		    close(sockconn);
		    continue;

	    }

	    setsockopt(sockconn, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t));
		
	    // 将线程设置为分离状态(detach state),以保证线程结束后其占用的资源会被系统自动释放

	    pthread_detach(tid);

	    printf("客户端(%s:%d)已连接！\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

    }


    // 7.关闭监听套接字
    close(socklisten);

}



// 定义线程函数
static void* do_device_comm(void *arg)
{
	int sockconn = (long)arg;

        // 5.收发数据
 
        char data[500];
	int ret;
	char *p = NULL;
	const char *topic = NULL, *payload = NULL;

	char dev_id[33];	//设备ID最长32个字符，最短3个字符!
	int len;
	
	while((ret = recv_line(sockconn, data, sizeof(data))) > 0)
	{
		printf("客户端说: %s\n", data);
		
		p = strchr(data, ' ');
		
		if(p == NULL) break;
		*p = '\0';
		
		topic = data;
		payload = p + 1;

		if(*payload == '\0') break;

		p = (char*)strchr(topic, '/');
		if(p == NULL) break;
		
		// 校验设备ID的长度是否合法
		len = p - topic;
		if(len < 3 || len > 32) break;
		
		strncpy(dev_id, topic, len);
		dev_id[len] = '\0';

		// 数据用 map 容器存起来
		online_devs[dev_id] = sockconn;

		mqttclient_pub_msg(topic, payload);

	}
	
   	 // 6.端开连接
   	 close(sockconn);
       
	return NULL;
}


int device_send_cmd(const char *dev_id, const void *cmd, int size) 
{
	auto it = online_devs.find(dev_id);
	if(it == online_devs.end()) return 1;
	
	// 如果目标设备在线，就转发控制指令
	if(send(online_devs[dev_id], cmd, size, 0) > 0)
		return 0;
	
	return 2;

}


// 接收一行数据
static int recv_line(int sock, char *buff, int size)
{
	char c = 0;
	int cnt = 0, ret;

	while(cnt < size - 1)
	{
		ret = recv(sock, &c, 1, 0);

		if(ret < 0) return -1;

		if(ret == 0 || c == '\n') break;
		

		buff[cnt] = c;
		cnt++;
	}

	if(c != '\n') return -1;

	buff[cnt] = '\0';

	return cnt;
}
